Java开发中商业计算请务必使用BigDecimal来进行计算!
1.
前言
今天群里一个初级开发者问为什么测试人员测出来他写的价格计算模块有计算偏差的问题,他检查了半天也没找出问题。这里小胖哥要提醒你,商业计算请务必使用`BigDecimal`,浮点做商业运算是不精确的。因为计算机无法使用二进制小数来精确描述我们程序中的十进制小数。《Effective Java》在第48条也推荐“使用BigDecimal来做精确运算”。今天我们就来总结归纳其相关的知识点。
2.
BigDecimal
BigDecimal表示不可变的任意精度带符号十进制数。它由两部分组成:
intVal - 未校正精度的整数,类型为`BigInteger`
Scale - 一个32位整数,表示小数点右边的位数
3.
构造BigDecimal实例
public void theValueMatches() {
BigDecimal bdFromString = new BigDecimal("0.12");
BigDecimal bdFromCharArray = new BigDecimal(new char[]{'3', '.', '1', '4', '1', '5'});
BigDecimal bdlFromInt = new BigDecimal(42);
BigDecimal bdFromLong = new BigDecimal(123412345678901L);
BigInteger bigInteger = BigInteger.probablePrime(100, new Random());
BigDecimal bdFromBigInteger = new BigDecimal(bigInteger);
assertEquals("0.12", bdFromString.toString());
assertEquals("3.1415", bdFromCharArray.toString());
assertEquals("42", bdlFromInt.toString());
assertEquals("123412345678901", bdFromLong.toString());
assertEquals(bigInteger.toString(), bdFromBigInteger.toString());
}
public void whenBigDecimalCreatedFromDouble_thenValueMayNotMatch() {
BigDecimal bdFromDouble = new BigDecimal(0.1d);
assertNotEquals("0.1", bdFromDouble.toString());
}
public void whenBigDecimalCreatedUsingValueOf_thenValueMatches() {
BigDecimal bdFromDouble = BigDecimal.valueOf(0.1d);
BigDecimal bigFromLong=BigDecimal.valueOf(1,1);
assertEquals("0.1", bdFromDouble.toString());
assertEquals("0.1", bigFromLong.toString());
}
4.
常用API
方法名 | 对应方法相关用法解释 |
abs() | 绝对值,scale不变 |
add(BigDecimal augend) | 加,scale为augend和原值scale的较大值 |
subtract(BigDecimal augend) | 减,scale为augend和原值scale的较大值 |
multiply(BigDecimal multiplicand) | 乘,scale为augend和原值scale的和 |
divide(BigDecimal divisor) | 除,原值/divisor,如果不能除尽会抛出异常,scale与原值一致 |
divide(BigDecimal divisor, int roundingMode) | 除,指定舍入方式,scale与原值一致 |
divide(BigDecimal divisor, int scale, int roundingMode) | 除,指定舍入方式和scale |
remainder(BigDecimal divisor) | 取余,scale与原值一致 |
divideAndRemainder(BigDecimal divisor) | 除法运算后返回一个数组存放除尽和余数 如 23/3 返回 {7,2} |
divideToIntegralValue(BigDecimal divisor) | 除,只保留整数部分,但scale仍与原值一致 |
max(BigDecimal val) | 较大值,返回原值与val中的较大值,与结果的scale一致 |
min(BigDecimal val) | 较小值,与结果的scale一致 |
movePointLeft(int n) | 小数点左移,scale为原值scale+n |
movePointRight(int n) | 小数点右移,scale为原值scale+n |
negate() | 取反,scale不变 |
pow(int n) | 幂,原值^n,原值的n次幂 |
scaleByPowerOfTen(int n) | 相当于小数点右移n位,原值*10^n |
5.
BigDecimal操作
5.1
提取属性
public void whenGettingAttributes_thenExpectedResult() {
BigDecimal bd = new BigDecimal("-12345.6789");
assertEquals(9, bd.precision());
assertEquals(4, bd.scale());
assertEquals(-1, bd.signum());
}
5.2
比较大小
public void whenComparingBigDecimals_thenExpectedResult() {
BigDecimal bd1 = new BigDecimal("1.0");
BigDecimal bd2 = new BigDecimal("1.00");
BigDecimal bd3 = new BigDecimal("2.0");
assertTrue(bd1.compareTo(bd3) < 0);
assertTrue(bd3.compareTo(bd1) > 0);
assertTrue(bd1.compareTo(bd2) == 0);
assertTrue(bd1.compareTo(bd3) <= 0);
assertTrue(bd1.compareTo(bd2) >= 0);
assertTrue(bd1.compareTo(bd3) != 0);
}
@Test
public void whenEqualsCalled_thenSizeAndScaleMatched() {
BigDecimal bd1 = new BigDecimal("1.0");
BigDecimal bd2 = new BigDecimal("1.00");
assertFalse(bd1.equals(bd2));
}
5.3
四则运算
add ——加法
subtract ——减法
divide ——除法,有可能除不尽,必须显式声明保留小数位数避免抛出`ArithmeticException`异常
multiply ——乘法
public void whenPerformingArithmetic_thenExpectedResult() {
BigDecimal bd1 = new BigDecimal("4.0");
BigDecimal bd2 = new BigDecimal("2.0");
BigDecimal sum = bd1.add(bd2);
BigDecimal difference = bd1.subtract(bd2);
BigDecimal quotient = bd1.divide(bd2);
BigDecimal product = bd1.multiply(bd2);
assertTrue(sum.compareTo(new BigDecimal("6.0")) == 0);
assertTrue(difference.compareTo(new BigDecimal("2.0")) == 0);
assertTrue(quotient.compareTo(new BigDecimal("2.0")) == 0);
assertTrue(product.compareTo(new BigDecimal("8.0")) == 0);
}
5.4
四舍五入
RoundingMode.UP:以小数位为原点 是正数取右边,负数取左边
RoundingMode.DOWN:以小数位为原点 也就是正数取左边,负数取右边
RoundingMode.FLOOR:取左边最近的正数
RoundingMode.CEILING:取右边最近的整数
RoundingMode.HALF_DOWN:五舍六入,负数先取绝对值再五舍六入再负数
RoundingMode.HALF_UP:四舍五入,负数原理同上
RoundingMode.HALF_EVEN:这个比较绕,整数位若是奇数则四舍五入,若是偶数则五舍六入
RoundingMode.ROUND_UNNECESSARY:不需要取整,如果存在小数位,就抛ArithmeticException 异常
6.
格式化
6.1
NumberFormat
NumberFormat.getInstance(Locale)、getNumberInstance(Locale)。返回指定语言环境的通用数值格式。
NumberFormat.getCurrencyInstance(Locale)。返回指定语言环境的货币格式。
NumberFormat.getPercentInstance(Locale)。返回指定语言环境的百分比格式。
NumberFormat.getIntegerInstance(Locale)。返回指定语言环境的整数数值格式。
NumberFormat.setMinimumIntegerDigits(int)。设置数的整数部分所允许的最小位数。
NumberFormat.setMaximumIntegerDigits(int)。设置数的整数部分所允许的最大位数。
NumberFormat.setMinimumFractionDigits(int)。设置最少小数点位数,不足的位数以0补位,超出的话按实际位数输出。
NumberFormat.setMaximumFractionDigits(int)。设置最多保留小数位数,不足不补0。
6.2
DecimalFormat
“0”——表示一位数值,如没有,显示0。如“0000.0000”,整数位或小数位>4,按实际输出,<4整数位前面补0小数位后面补0,凑足4位。
“#”——表示任意位数的整数。如没有,则不显示。在小数点位使用,只表示一位小数,超出部分四舍五入。如:“#”:无小数,小数部分四舍五入。“.#”:整数部分不变,一位小数,四舍五入。“.##”:整数部分不变,二位小数,四舍五入。
“.”——表示小数点。注意一个pattern中只能出现一次,超过一次将格式化异常。
“,”——与模式“0”一起使用,表示逗号。注意一定不能在小数点后用,否则格式化异常。
7.
总结